// $Id: buf.c 6 2011-11-19 06:33:05Z cedric.shih@gmail.com $
/*
 * Copyright 2011 Cedric Shih (cedric dot shih at gmail dot com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>

#include "log.h"
#include "heap.h"

#include "buf.h"

#define MIN(a,b) a > b ? b : a

#ifdef WIN32
#define EWOULDBLOCK EAGAIN
#endif

struct zcbuffer *zcbuffer_new(size_t capacity)
{
	struct zcbuffer *buffer;

	zcbuffer_debug("constructing: %d bytes", capacity);

	if (!(buffer = calloc(1, sizeof(*buffer)))) {
		zcbuffer_error("failed to calloc");
		goto error;
	}

	zcbuffer_init(buffer);

	if (capacity > 0) {
		buffer->type = ZCBUFFER_DIRECT;

		if (!(buffer->data = malloc(capacity))) {
			zcbuffer_error("failed to malloc: %d bytes", capacity);
			goto error;
		}
	} else {
		capacity = 0;
		buffer->type = ZCBUFFER_MAPPED;
	}

	buffer->limit = buffer->capacity = capacity;
	return buffer;

error:
	zcbuffer_free(buffer);
	return NULL;
}

void zcbuffer_init(struct zcbuffer *buffer)
{
	memset(buffer, '\0', sizeof(*buffer));
	TAILQ_INIT(&buffer->clones);
	TAILQ_INIT(&buffer->caches);
}

int zcbuffer_map(struct zcbuffer *buffer, unsigned char *data, size_t len)
{
	int rc;

	switch (buffer->type) {
	case ZCBUFFER_MAPPED:
		break;
	case ZCBUFFER_DIRECT:
	case ZCBUFFER_ROTATED:
	case ZCBUFFER_CLONED:
		rc = EPERM;
		zcbuffer_error("not supported");
		goto finally;
	case ZCBUFFER_UNKNOWN:
	default:
		rc = ENOSYS;
		zcbuffer_error("unexpected type: %d", buffer->type);
		goto finally;
	}

	zcbuffer_trace("mapping %p: %d@%p", buffer, len, data);

	buffer->data = data;
	buffer->limit = buffer->capacity = len;
	buffer->position = 0;
	buffer->mark = -1;
	rc = 0;

finally:
	return rc;
}

struct zcbuffer_list *zcbuffer_cache(struct zcbuffer *buffer)
{
	int rc;
	struct zcbuffer_list *caches = NULL;

	switch (buffer->type) {
	case ZCBUFFER_DIRECT:
	case ZCBUFFER_MAPPED:
		zcbuffer_trace("cache from source buffer");
		caches = &buffer->caches;
		break;
	case ZCBUFFER_ROTATED:
		zcbuffer_trace("cache from source heap");
		caches = &buffer->source.heap->caches;
		break;
	case ZCBUFFER_CLONED:
		zcbuffer_trace("resolving cache");
		caches = zcbuffer_cache(buffer->source.buffer);
		break;
	case ZCBUFFER_UNKNOWN:
	default:
		rc = ENOSYS;
		zcbuffer_error("unexpected type: %d", buffer->type);
		goto finally;
	}

	rc = 0;

finally:
	if (rc != 0) {
		errno = rc;
	}
	return caches;
}

void zcbuffer_set_cb(struct zcbuffer *buffer,
		zcbuffer_cb cb, void *cbarg)
{
	if (!buffer) {
		zcbuffer_error("null argument(s)");
		return;
	}

	buffer->cb = cb;
	buffer->cbarg = cbarg;
}

void zcbuffer_free(struct zcbuffer *buffer)
{
	int rc;
	struct zcbuffer_item *item, *next;
	struct zcbuffer_list *caches;
	struct zcbuffer *clone;

	if (!buffer) {
		return;
	}

	zcbuffer_trace("destructing: %p", buffer);

	if (buffer->cb) {
		zcbuffer_trace("invoking %p for before releasing buffer",
				buffer->cb);
		(*buffer->cb)(buffer, ZCBUFFER_EVENT_RELEASING, buffer->cbarg);
	}

	switch (buffer->type) {
	case ZCBUFFER_DIRECT:
		zcbuffer_trace("direct buffer: %p", buffer);

		if (buffer->data) {
			free(buffer->data);
		}

		free(buffer);
		break;
	case ZCBUFFER_MAPPED:
		zcbuffer_trace("mapped buffer: %p", buffer);
		free(buffer);
		break;
	case ZCBUFFER_CLONED:
		zcbuffer_trace("cloned buffer: %p", buffer);

		if (!(caches = zcbuffer_cache(buffer))) {
			rc = errno ? errno : -1;
			zcbuffer_error("failed to resolve cache: %s",
					strerror(rc));
			break;
		}

		zcbuffer_debug("removing from clone list");

		if ((rc = zcbuffer_list_remove(&buffer->source.buffer->clones,
				buffer))) {
			zcbuffer_error("failed to remove: %s", strerror(rc));
			break;
		}

		zcbuffer_debug("recycling back to cache");

		if ((rc = zcbuffer_list_add(caches, buffer))) {
			zcbuffer_error("failed to add: %s", strerror(rc));
			break;
		}
		break;
	case ZCBUFFER_ROTATED:
		// do nothing
		break;
	case ZCBUFFER_UNKNOWN:
	default:
		zcbuffer_error("unexpected type: %d", buffer->type);
		break;
	}

	TAILQ_FOREACH_SAFE(item, &buffer->clones, entry, next) {
		clone = item->buffer;

		zcbuffer_warn("detaching dirty clone with memory copy: %p",
				clone);

		if (!(clone->data = malloc(buffer->capacity))) {
			zcbuffer_error("malloc: %s", strerror(errno));
			clone->type = ZCBUFFER_MAPPED;
			clone->data = buffer->data;
		} else {
			clone->type = ZCBUFFER_DIRECT;
			memcpy(clone->data, buffer->data, buffer->capacity);
		}

		if ((rc = zcbuffer_list_remove(&buffer->clones, clone))) {
			zcbuffer_error("failed to remove: %s", strerror(rc));
			continue;
		}
	}

	zcbuffer_list_clear_caches(&buffer->caches);
}

struct zcbuffer *zcbuffer_clone(struct zcbuffer *buffer)
{
	int rc;
	struct zcbuffer_list *caches = NULL;
	struct zcbuffer *clone = NULL;

	zcbuffer_trace("cloning %p", buffer);

	if (!(caches = zcbuffer_cache(buffer))) {
		rc = errno ? errno : -1;
		zcbuffer_error("failed to resolve cache: %s",
				strerror(rc));
		goto error;
	}

	if (TAILQ_EMPTY(caches)) {
		if ((rc = zcbuffer_list_add_cache(caches, &clone))) {
			zcbuffer_error("failed to add cache: %s", strerror(rc));
			goto error;
		}
	}

	clone = TAILQ_FIRST(caches)->buffer;

	if ((rc = zcbuffer_list_remove(caches, clone))) {
		zcbuffer_error("failed to remove: %s", strerror(rc));
		goto error;
	}

	if ((rc = zcbuffer_list_add(&buffer->clones, clone))) {
		zcbuffer_error("failed to add: %s", strerror(rc));
		goto error;
	}

	clone->type = ZCBUFFER_CLONED;
	clone->source.buffer = buffer;
	clone->data = buffer->data;
	clone->capacity = buffer->capacity;
	clone->limit = buffer->limit;
	clone->position = buffer->position;
	clone->mark = clone->mark;

	zcbuffer_trace("cloned from %p: %p", buffer, clone);
	return clone;

error:
	zcbuffer_free(clone);
	errno = rc;
	return NULL;
}

struct zcbuffer *zcbuffer_slice(struct zcbuffer *buffer,
		size_t position, size_t capacity)
{
	struct zcbuffer *clone = NULL;

	if (!buffer) {
		zcbuffer_error("null argument(s)");
		goto error;
	}

	zcbuffer_debug("slicing %d bytes at %d from %d@%p", capacity, position,
			buffer->capacity, buffer->data);

	if (capacity > (zcbuffer_capacity(buffer) - position)) {
		zcbuffer_error("underflows");
		goto error;
	}

	if (!(clone = zcbuffer_clone(buffer))) {
		zcbuffer_error("failed to clone");
		goto error;
	}

	clone->data = buffer->data + position;
	clone->offset = position;
	clone->limit = clone->capacity = capacity;
	clone->position = 0;
	clone->mark = 0;

	return clone;

error:
	zcbuffer_free(clone);
	return NULL;
}

int zcbuffer_peek(struct zcbuffer *buffer, size_t position, int *b)
{
	int rc;

	if (!buffer || !b) {
		zcbuffer_error("null argument(s)");
		rc = EINVAL;
		goto finally;
	}

	if ((buffer->limit - position) <= 0) {
		zcbuffer_error("underflows");
		rc = ERANGE;
		goto finally;
	}

	zcbuffer_trace("getting at: %d", position);

	switch (buffer->type) {
	case ZCBUFFER_DIRECT:
	case ZCBUFFER_MAPPED:
	case ZCBUFFER_ROTATED:
	case ZCBUFFER_CLONED:
		*b = *(buffer->data + position);
		break;
	case ZCBUFFER_UNKNOWN:
	default:
		zcbuffer_error("unexpected type: %d", buffer->type);
		rc = ENOSYS;
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

int zcbuffer_get(struct zcbuffer *buffer, int *b)
{
	int rc;

	if ((rc = zcbuffer_peek(buffer, buffer->position, b)) == 0) {
		buffer->position++;
	}

	return rc;
}

int zcbuffer_peeks(struct zcbuffer *buffer, size_t position,
		unsigned char *data, size_t len)
{
	int rc;

	if (!buffer || !data || !len) {
		zcbuffer_error("null or zero argument(s)");
		rc = EINVAL;
		goto finally;
	}

	if (len > (buffer->limit - position)) {
		zcbuffer_error("underflows");
		rc = ERANGE;
		goto finally;
	}

	zcbuffer_debug("getting %d bytes at: %d", len, position);

	switch (buffer->type) {
	case ZCBUFFER_DIRECT:
	case ZCBUFFER_MAPPED:
	case ZCBUFFER_ROTATED:
	case ZCBUFFER_CLONED:
		memcpy(data, buffer->data + position, len);
		break;
	case ZCBUFFER_UNKNOWN:
	default:
		zcbuffer_error("unexpected type: %d", buffer->type);
		rc = ENOSYS;
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

int zcbuffer_peek_u16(struct zcbuffer *buffer, size_t position, u_int16_t *i)
{
	int rc;
	union {
		unsigned char bytes[2];
		u_int16_t value;
	} data;

	if ((rc = zcbuffer_peeks(buffer, position,
			data.bytes, sizeof(data.bytes)))) {
		goto finally;
	}

	*i = ntohs(data.value);
	rc = 0;

finally:
	return rc;
}

int zcbuffer_peek_u32(struct zcbuffer *buffer, size_t position, u_int32_t *i)
{
	int rc;
	union {
		unsigned char bytes[4];
		u_int32_t value;
	} data;

	if ((rc = zcbuffer_peeks(buffer, position,
			data.bytes, sizeof(data.bytes)))) {
		goto finally;
	}

	*i = ntohl(data.value);
	rc = 0;

finally:
	return rc;
}

int zcbuffer_gets(struct zcbuffer *buffer, unsigned char *data, size_t len)
{
	int rc;

	if ((rc = zcbuffer_peeks(buffer, buffer->position, data, len)) == 0) {
		buffer->position += len;
	}

	return rc;
}

int zcbuffer_get_u16(struct zcbuffer *buffer, u_int16_t *i)
{
	int rc;
	union {
		unsigned char bytes[2];
		u_int16_t value;
	} data;

	if ((rc = zcbuffer_gets(buffer, data.bytes, sizeof(data.bytes)))) {
		goto finally;
	}

	*i = ntohs(data.value);
	rc = 0;

finally:
	return rc;
}

int zcbuffer_get_u32(struct zcbuffer *buffer, u_int32_t *i)
{
	int rc;
	union {
		unsigned char bytes[4];
		u_int32_t value;
	} data;

	if ((rc = zcbuffer_gets(buffer, data.bytes, sizeof(data.bytes)))) {
		goto finally;
	}

	*i = ntohl(data.value);
	rc = 0;

finally:
	return rc;
}

int zcbuffer_skip(struct zcbuffer *buffer, size_t offset)
{
	int rc;

	if (!buffer) {
		zcbuffer_error("null or zero argument(s)");
		rc = EINVAL;
		goto finally;
	}

	if (offset > (buffer->limit - buffer->position)) {
		zcbuffer_error("underflows");
		rc = ERANGE;
		goto finally;
	}

	zcbuffer_debug("skipping: %d", offset);

	switch (buffer->type) {
	case ZCBUFFER_DIRECT:
	case ZCBUFFER_MAPPED:
	case ZCBUFFER_ROTATED:
	case ZCBUFFER_CLONED:
		break;
	case ZCBUFFER_UNKNOWN:
	default:
		zcbuffer_error("unexpected type: %d", buffer->type);
		rc = ENOSYS;
		goto finally;
	}

	buffer->position += offset;
	rc = 0;

finally:
	return rc;
}

int zcbuffer_put(struct zcbuffer *buffer, int b)
{
	int rc;
	size_t remain;

	if (!buffer) {
		zcbuffer_error("null argument");
		rc = EINVAL;
		goto finally;
	}

	if ((remain = (buffer->limit - buffer->position)) <= 0) {
		zcbuffer_error("out of buffer: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
		rc = EOVERFLOW;
		goto finally;
	}

	zcbuffer_debug("putting at: %d", buffer->position);

	switch (buffer->type) {
	case ZCBUFFER_MAPPED:
	case ZCBUFFER_ROTATED:
	case ZCBUFFER_CLONED:
		zcbuffer_warn("attempting to modify non-direct buffer: %p",
				buffer);
	case ZCBUFFER_DIRECT:
		*(buffer->data + buffer->position) = b;
		break;
	case ZCBUFFER_UNKNOWN:
	default:
		zcbuffer_error("unexpected type: %d", buffer->type);
		rc = ENOSYS;
		goto finally;
	}

	buffer->position++;
	rc = 0;

finally:
	return rc;
}

int zcbuffer_puts(struct zcbuffer *buffer,
		const unsigned char *data, size_t len)
{
	int rc;
	size_t remaining;

	if (!buffer || !data || len <= 0) {
		zcbuffer_error("null or zero argument(s)");
		rc = EINVAL;
		goto finally;
	}

	if ((remaining = (buffer->limit - buffer->position)) < len) {
		zcbuffer_error("overflows: %d > %d", len, remaining);
		rc = EOVERFLOW;
		goto finally;
	}

	zcbuffer_debug("putting %d bytes at: %d", len, buffer->position);

	switch (buffer->type) {
	case ZCBUFFER_MAPPED:
	case ZCBUFFER_CLONED:
		zcbuffer_warn("attempting to modify non-direct buffer: %p",
				buffer);
	case ZCBUFFER_ROTATED:
	case ZCBUFFER_DIRECT:
		memcpy(buffer->data + buffer->position, data, len);
		break;
	case ZCBUFFER_UNKNOWN:
	default:
		zcbuffer_error("unexpected type: %d", buffer->type);
		rc = ENOSYS;
		goto finally;
	}

	buffer->position += len;
	rc = 0;

finally:
	return rc;
}

static const char alphabets[] =
		"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char padding = '=';

int zcbuffer_puts_b64(struct zcbuffer *buffer,
		const unsigned char *data, size_t len)
{
	int rc;
	unsigned char raw[3];
	char enc[4];
	size_t off, rem, i;

	if (!buffer || !data || !len) {
		zcbuffer_error("null or zero argument(s)");
		rc = EINVAL;
		goto finally;
	}

	off = 0;

	while ((rem = len - off) >= sizeof(raw)) {
		memcpy(raw, data + off, sizeof(raw));
		off += sizeof(raw);

		enc[0] = alphabets[raw[0] >> 2];
		enc[1] = alphabets[((raw[0] & 0x03) << 4) + (raw[1] >> 4)];
		enc[2] = alphabets[((raw[1] & 0x0f) << 2) + (raw[2] >> 6)];
		enc[3] = alphabets[raw[2] & 0x3f];

		if ((rc = zcbuffer_puts(buffer, (unsigned char *)enc,
				sizeof(enc)))) {
			zcbuffer_error("failed to puts at %d: %s",
					off, strerror(rc));
			goto finally;
		}
	}

	if (rem != 0) {
		memset(raw, '\0', sizeof(raw));

		for (i = 0; i < rem; i++) {
			raw[i] = data[off++];
		}

		enc[0] = alphabets[raw[0] >> 2];
		enc[1] = alphabets[((raw[0] & 0x03) << 4) + (raw[1] >> 4)];

		switch (rem) {
		case 1:
			enc[2] = padding;
			enc[3] = padding;
			break;
		case 2:
			enc[2] = alphabets[((raw[1] & 0x0f) << 2) + (raw[2] >> 6)];
			enc[3] = padding;
			break;
		}

		if ((rc = zcbuffer_puts(buffer, (unsigned char *)enc,
				sizeof(enc)))) {
			zcbuffer_error("failed to puts at %d: %s",
					off, strerror(rc));
			goto finally;
		}
	}

	rc = 0;

finally:
	return rc;
}

int zcbuffer_putf(struct zcbuffer *buffer, const char *fmt, ...)
{
	int rc, len;
	va_list ap;
	size_t remain;

	if (!buffer || !fmt) {
		zcbuffer_error("null argument(s)");
		rc = EINVAL;
		goto finally;
	}

	if ((remain = (buffer->limit - buffer->position)) <= 0) {
		zcbuffer_error("out of buffer: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
		rc = EOVERFLOW;
		goto finally;
	}

	zcbuffer_debug("putting formatted at: %d", buffer->position);

	switch (buffer->type) {
	case ZCBUFFER_MAPPED:
	case ZCBUFFER_ROTATED:
	case ZCBUFFER_CLONED:
		zcbuffer_warn("attempting to modify non-direct buffer: %p",
				buffer);
	case ZCBUFFER_DIRECT:
		break;
	case ZCBUFFER_UNKNOWN:
	default:
		zcbuffer_error("unexpected type: %d", buffer->type);
		rc = ENOSYS;
		goto finally;
	}

	va_start(ap, fmt);
	len = vsnprintf((char *)(buffer->data + buffer->position),
			remain, fmt, ap);
	va_end(ap);

	if (len >= remain) {
		zcbuffer_error("overflows: %d >= %d", len, remain);
		rc = EOVERFLOW;
		goto finally;
	} else if (len < 0) {
		zcbuffer_error("failed to printf");
		rc = errno ? errno : -1;
		goto finally;
	}

	buffer->position += len;
	rc = 0;

finally:
	return rc;
}

ssize_t zcbuffer_read(struct zcbuffer *buffer, int fd)
{
	int rc;
	ssize_t len;
	size_t remaining;

	if (!buffer) {
		zcbuffer_error("null argument");
		rc = EINVAL;
		goto finally;
	}

	if ((remaining = (buffer->limit - buffer->position)) <= 0) {
		zcbuffer_error("out of buffer: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
		rc = EOVERFLOW;
		goto finally;
	}

	zcbuffer_debug("reading socket %d: pos=%d, limit=%d, mark=%d", fd,
			buffer->position, buffer->limit, buffer->mark);

	switch (buffer->type) {
	case ZCBUFFER_MAPPED:
	case ZCBUFFER_ROTATED:
	case ZCBUFFER_CLONED:
		zcbuffer_warn("attempting to modify non-direct buffer: %p",
				buffer);
	case ZCBUFFER_DIRECT:
		if ((len = read(fd, buffer->data + buffer->position,
				remaining)) < 0) {
			rc = errno ? errno : -1;
			zcbuffer_debug("failed to read: %s", strerror(rc));
			goto finally;
		}

		buffer->position += len;

		zcbuffer_debug("%d bytes read from socket %d: "
				"pos=%d, limit=%d, mark=%d", len, fd,
				buffer->position, buffer->limit, buffer->mark);
		break;
	case ZCBUFFER_UNKNOWN:
	default:
		zcbuffer_error("unexpected type: %d", buffer->type);
		rc = ENOSYS;
		goto finally;
	}

	rc = 0;

finally:
	if (rc != 0) {
		len = -1;
		errno = rc;
	}
	return len;
}

ssize_t zcbuffer_write(struct zcbuffer *buffer, int fd)
{
	int rc;
	ssize_t len;
	size_t remaining;

	if (!buffer) {
		zcbuffer_error("null argument");
		rc = EINVAL;
		goto finally;
	}

	if ((remaining = (buffer->limit - buffer->position)) <= 0) {
		zcbuffer_error("out of buffer: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
		rc = EOVERFLOW;
		goto finally;
	}

	zcbuffer_debug("writing to socket %d: pos=%d, limit=%d, mark=%d", fd,
			buffer->position, buffer->limit, buffer->mark);

	switch (buffer->type) {
	case ZCBUFFER_DIRECT:
	case ZCBUFFER_MAPPED:
	case ZCBUFFER_ROTATED:
	case ZCBUFFER_CLONED:
		if ((len = write(fd, buffer->data + buffer->position,
				(buffer->limit - buffer->position))) < 0) {
			rc = errno ? errno : -1;
			zcbuffer_debug("failed to write: %s", strerror(rc));
			goto finally;
		}

		buffer->position += len;

		zcbuffer_debug("%d bytes written to socket %d: "
				"pos=%d, limit=%d, mark=%d", len, fd,
				buffer->position, buffer->limit,
				buffer->mark);
		break;
	case ZCBUFFER_UNKNOWN:
	default:
		zcbuffer_error("unexpected type: %d", buffer->type);
		rc = ENOSYS;
		goto finally;
	}

	rc = 0;

finally:
	if (rc != 0) {
		len = -1;
		errno = rc;
	}
	return len;
}

int zcbuffer_load(struct zcbuffer *buffer, struct zcbuffer *source)
{
	int rc;
	size_t len;

	if (!buffer || !source) {
		zcbuffer_error("null argument(s)");
		rc = EINVAL;
		goto finally;
	}

	zcbuffer_debug("loading from: pos=%d, limit=%d, mark=%d",
			source->position, source->limit, source->mark);
	zcbuffer_debug("loading to: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);

	len = zcbuffer_remaining(source);

	if (len > (buffer->limit - buffer->position)) {
		zcbuffer_error("overflows: %d > %d", len,
				buffer->limit - buffer->position);
		rc = EOVERFLOW;
		goto finally;
	}

	switch (buffer->type) {
	case ZCBUFFER_MAPPED:
	case ZCBUFFER_ROTATED:
	case ZCBUFFER_CLONED:
		zcbuffer_warn("attempting to modify non-direct buffer: %p",
				buffer);
	case ZCBUFFER_DIRECT:
		if ((rc = zcbuffer_gets(source,
				buffer->data + buffer->position, len))) {
			zcbuffer_error("failed to gets: %s", strerror(rc));
			goto finally;
		}
		buffer->position += len;
		break;
	case ZCBUFFER_UNKNOWN:
	default:
		zcbuffer_error("unexpected type: %d", buffer->type);
		rc = ENOSYS;
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

inline unsigned char *zcbuffer_array(struct zcbuffer *buffer)
{
	unsigned char *data;

	switch (buffer->type) {
	case ZCBUFFER_DIRECT:
	case ZCBUFFER_MAPPED:
	case ZCBUFFER_ROTATED:
		data = buffer->data;
		break;
	case ZCBUFFER_CLONED:
		data = buffer->source.buffer->data;
		break;
	case ZCBUFFER_UNKNOWN:
	default:
		zcbuffer_error("unexpected type: %d", buffer->type);
		data = NULL;
		break;
	}

	return data;
}

inline size_t zcbuffer_capacity(struct zcbuffer *buffer)
{
	return buffer->capacity;
}

inline size_t zcbuffer_position(struct zcbuffer *buffer)
{
	return buffer->position;
}

int zcbuffer_set_position(struct zcbuffer *buffer, size_t position)
{
	if (!buffer) {
		zcbuffer_error("null argument");
		return EINVAL;
	}

	if (position > buffer->limit || position > buffer->capacity) {
		zcbuffer_error("out of range");
		return ERANGE;
	}

	zcbuffer_debug("setting position to %d: pos=%d, limit=%d, mark=%d",
			position, buffer->position, buffer->limit,
			buffer->mark);

	buffer->position = position;
	return 0;
}

inline size_t zcbuffer_limit(struct zcbuffer *buffer)
{
	return buffer->limit;
}

int zcbuffer_set_limit(struct zcbuffer *buffer, size_t limit)
{
	if (!buffer) {
		zcbuffer_error("null argument");
		return EINVAL;
	}

	if (limit > buffer->capacity) {
		zcbuffer_error("out of range");
		return ERANGE;
	}

	zcbuffer_debug("setting limit to %d: pos=%d, limit=%d, mark=%d",
			limit, buffer->position, buffer->limit, buffer->mark);

	if (limit < buffer->position) {
		buffer->position = limit;
	}

	buffer->limit = limit;
	return 0;
}

inline size_t zcbuffer_remaining(struct zcbuffer *buffer)
{
	return buffer->limit - buffer->position;
}

void zcbuffer_clear(struct zcbuffer *buffer)
{
	zcbuffer_debug("clearing: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);

	buffer->limit = buffer->capacity;
	buffer->position = 0;
	buffer->mark = -1;
}

void zcbuffer_flip(struct zcbuffer *buffer)
{
	zcbuffer_debug("flipping: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);

	zcbuffer_set_limit(buffer, buffer->position);
	zcbuffer_set_position(buffer, 0);
	buffer->mark = -1;
}

void zcbuffer_rewind(struct zcbuffer *buffer)
{
	zcbuffer_debug("rewinding: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
	zcbuffer_set_position(buffer, 0);
	buffer->mark = -1;
}

void zcbuffer_mark(struct zcbuffer *buffer)
{
	zcbuffer_debug("marking: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
	buffer->mark = buffer->position;
}

int zcbuffer_reset(struct zcbuffer *buffer)
{
	int rc;

	if (!buffer) {
		zcbuffer_error("null argument");
		rc = EINVAL;
		goto finally;
	}

	if (buffer->mark < 0) {
		zcbuffer_error("mark is not defined");
		rc = EPERM;
		goto finally;
	}

	zcbuffer_debug("resetting: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);
	zcbuffer_set_position(buffer, buffer->mark);
	rc = 0;

finally:
	return rc;
}

int zcbuffer_compact(struct zcbuffer *buffer)
{
	int rc;

	if (!buffer) {
		zcbuffer_error("null argument");
		rc = EINVAL;
		goto finally;
	}

	switch (buffer->type) {
	case ZCBUFFER_MAPPED:
	case ZCBUFFER_ROTATED:
	case ZCBUFFER_CLONED:
		zcbuffer_warn("attempting to modify non-direct buffer: %p",
				buffer);
	case ZCBUFFER_DIRECT:
		break;
	case ZCBUFFER_UNKNOWN:
	default:
		zcbuffer_error("unexpected type: %d", buffer->type);
		rc = ENOSYS;
		goto finally;
	}

	size_t remaining = zcbuffer_remaining(buffer);

	zcbuffer_debug("compacting: pos=%d, limit=%d, mark=%d",
			buffer->position, buffer->limit, buffer->mark);

	memmove(buffer->data, buffer->data + buffer->position, remaining);
	buffer->position = remaining;
	buffer->limit = buffer->capacity;
	rc = 0;

finally:
	return rc;
}

int zcbuffer_resize(struct zcbuffer *buffer, size_t capacity)
{
	int rc;
	unsigned char *data;

	if (!buffer || !capacity) {
		zcbuffer_error("null or zero argument(s)");
		rc = EINVAL;
		goto finally;
	}

	if (capacity < buffer->limit) {
		zcbuffer_error("underflows");
		rc = ERANGE;
		goto finally;
	}

	zcbuffer_debug("resizing: %d", capacity);

	switch (buffer->type) {
	case ZCBUFFER_DIRECT:
		zcbuffer_debug("reallocating %p: %d", buffer->data, capacity);

		if (!(data = realloc(buffer->data, capacity))) {
			zcbuffer_debug("failed to reallocate; falling back...");

			if (!(data = malloc(capacity))) {
				zcbuffer_error("failed to malloc: %d bytes", capacity);
				rc = ENOMEM;
				goto finally;
			}

			zcbuffer_debug("allocated: %p => %p", buffer->data, data);

			memcpy(data, buffer->data, MIN(capacity, buffer->capacity));
			free(buffer->data);
			buffer->data = data;
		} else {
			zcbuffer_debug("reallocated: %p => %p", buffer->data, data);
			buffer->data = data;
		}

		buffer->limit = MIN(buffer->limit, capacity);
		buffer->capacity = capacity;
		break;
	case ZCBUFFER_ROTATED:
		if ((rc = zcheap_realloc(buffer->source.heap, buffer, capacity))) {
			zcbuffer_error("failed to realloc: %s", strerror(rc));
			goto finally;
		}
		break;
	case ZCBUFFER_CLONED:
	case ZCBUFFER_MAPPED:
		zcbuffer_error("not supported");
		rc = EPERM;
		goto finally;
	case ZCBUFFER_UNKNOWN:
	default:
		zcbuffer_error("unexpected type: %d", buffer->type);
		rc = ENOSYS;
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}

void zcbuffer_hexdump(struct zcbuffer *buffer,
		enum zcbuffer_hexdump_format format,
		char *dest, size_t len)
{
	size_t i, limit;
	int off;

	if (!buffer || !dest) {
		zcbuffer_error("null pointer(s)");
		return;
	}

	limit = buffer->position + len;
	off = 0;

	for (i = buffer->position; i < limit; i++) {
		if (i < buffer->limit) {
			off += snprintf(dest + off, len - off, "%02x ", buffer->data[i]);
		} else {
			off += snprintf(dest + off, len - off, "** ");
		}

		if (off >= len) {
			break;
		}
	}

	if (off < len) {
		snprintf(dest + off, len - off, "\n");
	}
}

void zcbuffer_hexdump_file(struct zcbuffer *buffer,
		enum zcbuffer_hexdump_format format,
		FILE *file, size_t len)
{
	size_t i, limit;

	if (!buffer || !file) {
		zcbuffer_error("null pointer(s)");
		return;
	}

	limit = buffer->position + len;

	for (i = buffer->position; i < limit; i++) {
		if (i < buffer->limit) {
			fprintf(file, "%02x ", buffer->data[i]);
		} else {
			fprintf(file, "** ");
		}
	}

	fprintf(file, "\n");
}
